﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using LumenWorks.Framework.IO.Csv;
using DataProcessor.Core;
using System.Text.RegularExpressions;
using LinqLib.Sequence;

namespace DataProcessor.DeeplinkData
{
    public class HitProcessor
    {
        public List<PublicHit> Assignments { get; set; }

        public HitProcessor(string amtResultFile)
        {
            Assignments = new List<PublicHit>();
            AddInputFile(amtResultFile);
        }

        public HitProcessor(List<PublicHit> assignments)
        {
            Assignments = assignments;
        }

        public HitProcessor UseDatabaseComments(string commentsPath)
        {
            var comments = new CsvReader(new StreamReader(commentsPath), true).ToCsvDictionary()
                .Select(i => new { CommentId = i["CommentId"], Comment = i["Comment"].Replace("\"", "\"\"") });

            Assignments.Join(comments, a => a.CommentId, c => c.CommentId, (a, c) => new { Assignment = a, Comment = c })
                .ToList().ForEach(a => a.Assignment.Comment = a.Comment.Comment);
                
            return this;
        }

        public HitProcessor AddInputFile(string amtResultFile)
        {
            Assignments.AddRange(new CsvReader(new StreamReader(amtResultFile), true).ToCsvDictionary()
                .SelectMany(item =>
                    Enumerable.Range(1, GetNumberOfComments(item["Input.COMMENT_IDS"])).Select(i =>
                        new PublicHit
                        {
                            Nature = item["Answer.Q-" + i + "-nature"],
                            Spirit = item["Answer.Q-" + i + "-spirit"],
                            TechType = item["Answer.Q-" + i + "-technical"],
                            Timecode = item["Answer.Q-" + i + "-tc"],
                            VideoId = item["Input.VIDEO_ID"],
                            Comment = GetComment(item["Input.COMMENTS"], i).Replace("\"", "\"\""),
                            CommentId = GetCommentId(item["Input.COMMENT_IDS"], i),
                            WorkerId = item["WorkerId"],
                            Validation = String.IsNullOrEmpty(item["Answer.Q-" + i + "-validation"]) ? "unselect" : item["Answer.Q-" + i + "-validation"],
                        }))
                .ToList());
            return this;
        }

        public HitProcessor FilterUnseriousWorkers()
        {
            var majorities = CalculateAggreements().ToCsvDictionary()
                .Select(i => new { CommentId = i["CommentId"], Majority = i["MajoritySpirit"] });

            var filtered = Assignments.Join(majorities, a => a.CommentId, m => m.CommentId, (a, m) => new { Assignment = a, Majority = m.Majority })
                // Filter those workers who skipped the spirit question while the majority have not skipped it
                .Where(a => a.Majority == "missing" || !String.IsNullOrEmpty(a.Assignment.Spirit))
                .Select(a => a.Assignment)
                .ToList();

            return new HitProcessor(filtered);
        }

        public HitProcessor FilterMismatchsInAnswers(string workersFile)
        { 
            // Accept rules
            Func<PublicHit, bool> rule1 = h => (h.Spirit != "loveit" && h.Spirit != "hateit") && h.Validation == "unselect";
            Func<PublicHit, bool> rule2 = h => (h.Spirit == "loveit") && (h.Validation == "loveittoo" || h.Validation == "opposite" || h.Validation == "not_sure"); 
            Func<PublicHit, bool> rule3 = h => h.Spirit == "hateit" && (h.Validation == "hateittoo" || h.Validation == "opposite" || h.Validation == "not_sure");

            Console.WriteLine("No. of comments: " + Assignments.Select(a => a.CommentId).Distinct().Count());
            Console.WriteLine("No. of assignment before mismatch filter: " + Assignments.Count());
            
            Assignments.ToList().ForEach(a => 
                {
                    if (rule1(a) || rule2(a) || rule3(a))
                        a.AcceptReject = "A";
                    else
                        a.AcceptReject = "R";
                });
            
            Console.WriteLine("No. of assignment after mismatch filter: " + Assignments.Where(a => a.AcceptReject == "A").Count());

            var workers = Assignments.Where(a => a.AcceptReject == "R")
                .GroupBy(a => a.WorkerId)
                .Select(g => new { WorkerId = g.Key, RejCount = g.Count() })
                .OrderByDescending(w => w.RejCount)
                .Select(w => String.Format("{0},{1}", w.WorkerId, w.RejCount));

            File.WriteAllLines(workersFile, new string[] { "WorkerId,RejectCount" }.Concat(workers));

            return new HitProcessor(Assignments.Where(a => a.AcceptReject == "A").ToList());
        }

        public void WorkersAnnotations(string outputFile)
        {
            File.WriteAllLines(outputFile, Assignments.Select(p => 
                string.Format("{0}\t{1}\t{2}", p.WorkerId, p.CommentId, String.IsNullOrEmpty(p.Spirit) ? "missing" : p.Spirit)));
        }

        public void SpiritCategories(string outputFile)
        {
            File.WriteAllLines(outputFile, Assignments.Select(a => a.Spirit).Distinct());
        }

        private string GetCommentId(string commentIds, int index)
        {
            return new Regex(@"(?<=\<span\sclass=.*?\>).*?(?=\<\/span\>)").Matches(commentIds)[index - 1].Value;
        }

        private string GetComment(string comments, int index)
        {
            return new Regex(@"(?<=\<span\sclass=""csvComment""\>).*?(?=\<\/span\>)").Matches(comments)[index - 1].Value;
        }

        public int GetNumberOfComments(string commentIds)
        {
            return new Regex(@"(?<=\<span\sclass=.*?\>).*?(?=\<\/span\>)").Matches(commentIds).Count;
        }

        public IEnumerable<string> CalculateAggreements(string outputFile = "")
        {
            var votes = Assignments.GroupBy(a => a.CommentId)
                .Select(g =>
                {
                    int A = g.Where(a => a.TechType == "A").Count();
                    int V = g.Where(a => a.TechType == "V").Count();
                    int AV = g.Where(a => a.TechType == "AV").Count();

                    //Spirit Types
                    int Info = g.Where(a => a.Spirit == "informational").Count();
                    int LoveIt = g.Where(a => a.Spirit == "loveit").Count();
                    int Whoops = g.Where(a => a.Spirit == "whoops").Count();
                    int Wow = g.Where(a => a.Spirit == "wow").Count();
                    int Question = g.Where(a => a.Spirit == "question").Count();
                    int HateIt = g.Where(a => a.Spirit == "hateit").Count();
                    int Missing = g.Where(a => String.IsNullOrEmpty(a.Spirit)).Count();

                    //Nature Types
                    int Sincerce = g.Where(a => a.Nature == "sincere").Count();
                    int Sarcastic = g.Where(a => a.Nature == "sarcastic").Count();
                    int Advertising = g.Where(a => a.Nature == "advertising").Count();

                    // Tc
                    int Tc = g.Where(a => a.Timecode == "tc").Count();
                    int NoTc = g.Where(a => a.Timecode == "notc").Count();
                    int UnknownLang = g.Where(a => a.Timecode == "unknown_language").Count();

                    return new
                    {
                        CommentId = g.Key,
                        VideoId = g.Select(a => a.VideoId).Distinct().Single(),
                        Comment = g.Select(a => a.Comment).Distinct().Single(),

                        //Tech types
                        A,
                        V,
                        AV,
                        SumTech = A + V + AV,

                        //Spirit Types
                        Info,
                        LoveIt,
                        Whoops,
                        Wow,
                        Question,
                        HateIt,
                        Missing,
                        SumSpirit = Info + LoveIt + Whoops + Wow + Question + HateIt + Missing,

                        //Nature Types
                        Sincerce,
                        Sarcastic,
                        Advertising,
                        SumNature = Sincerce + Sarcastic + Advertising,

                        //Tc
                        Tc, NoTc, UnknownLang,
                        SumTc = Tc + NoTc + UnknownLang,

                        MajorityTc = MajorityTc(Tc,NoTc,UnknownLang),
                        MajorityTech = MajorityTech(A, V, AV),
                        MajoritySpirit = MajoritySpirit(Info, LoveIt, Whoops, Wow, Question, HateIt, Missing),
                        MajorityNature = MajorityNature(Sincerce, Sarcastic, Advertising),
                        ConfSpirit = (double) g.Where(a => a.Spirit == MajoritySpirit(Info, LoveIt, Whoops, Wow, Question, HateIt, Missing)).Count() / g.Count()
                    };
                });


            var output = votes.Select(i => String.Format("{0},{1},{2},{3},{4},{5},{6},{7},{8},{9},{10},{11},{12},{13},{14},{15},{16},{17},{18},{19},{20},{21},{22},{23},{24},{25},{26:0.00},\"{27}\"",
                    i.VideoId, i.CommentId,
                    i.A, i.V, i.AV, i.SumTech,
                    i.Info, i.LoveIt, i.Whoops, i.Wow, i.Question, i.HateIt, i.Missing, i.SumSpirit,
                    i.Sincerce, i.Sarcastic, i.Advertising, i.SumNature,
                    i.Tc, i.NoTc, i.UnknownLang, i.SumTc,
                    i.MajorityTech, i.MajoritySpirit, i.MajorityNature, i.MajorityTc,
                    i.ConfSpirit,
                    i.Comment))
                .ToList();

            var header = new string[] { "VideoId,CommentId,A,V,AV,SumTech,Info,LoveIt,Whoops,Wow,Question,HateIt,Missing,SumSpirit,Sincerce,Sarcastic,Advertising,SumNature,Tc,NoTc,UnkownLang,SumTc,MajorityTech,MajoritySpirit,MajorityNature,MajorityTc,ConfSpirit,Comment" };
            
            //var evalData = output.Take(500);

            var result = header.Concat(output);//.Skip(500));

            if (!String.IsNullOrEmpty(outputFile))
            {
                File.WriteAllLines(outputFile, result);
                //File.WriteAllLines(outputFile + ".eva", header.Concat(evalData));
            }

            return result;
        }

        public String MajorityTech(params int[] values)
        { 
            var classes = new string[] {"A", "V", "AV"};
            var maxIx = values.IndexesOf(values.Max());

            if (maxIx.Count() == 1)
                return classes[maxIx.Single()];
            else
                return "NoAgreement";
        }

        public String MajorityTc(params int[] values)
        {
            var classes = new string[] { "tc", "notc", "unknown_language" };
            var maxIx = values.IndexesOf(values.Max());

            if (maxIx.Count() == 1)
                return classes[maxIx.Single()];
            else
                return "NoAgreement";
        }
        
        public String MajoritySpirit(params int[] values)
        {
            var classes = new string[] { "informational", "loveit", "whoops", "wow", "question", "hateit", "missing" };
            var maxIx = values.IndexesOf(values.Max());

            if (maxIx.Count() == 1)
                return classes[maxIx.Single()];
            else
                return "NoAgreement";
        }

        public String MajorityNature(params int[] values)
        {
            var classes = new string[] { "sincere", "sarcastic", "advertising" };
            var maxIx = values.IndexesOf(values.Max());

            if (maxIx.Count() == 1)
                return classes[maxIx.Single()];
            else
                return "NoAgreement";
        }

        public void PrintStatistics()
        {
            int totalWorkers = Assignments.GroupBy(a => a.WorkerId).Count();

            Console.WriteLine("Num Workers: " + totalWorkers);
        }

        public void PrintCommentPerVideoGraph(string videoData)
        {
            var videos = new CsvReader(new StreamReader(videoData), true).ToCsvDictionary()
                .Select(i => new { VideoId = i["VideoId"], Category = i["Category"] });

            var output = Assignments.Join(videos, a => a.VideoId, v => v.VideoId, (a, v) => new { a.VideoId, a.Nature, Category = v.Category })
                .GroupBy(a => a.Category)
                .Select(cg =>
                    cg.GroupBy(a => a.VideoId)
                    .Select(g => new { VideoId = g.Key, AllSame = g.Select(a => a.Nature).Distinct().Count() == 1 })
                    .GroupBy(v => v.AllSame)
                    .Select(g => new { Category = cg.Key, AllSame = g.Key, Count = g.Count() }))
                .SelectMany(cg => cg)
                .GroupBy(cg => cg.AllSame)
                .Select(cg => new { AllSame = cg.Key, Categories = cg.Select(c => new { Category = c.Category, Count = c.Count }) });
                
                
                var final = output.Select(cg => String.Format("{0}\t{1}", cg.AllSame, 
                    cg.Categories.Aggregate("", (cur, next) => cur + "\t" + next.Count)));

                //Console.WriteLine(output.Select(a => a.Categories.Aggregate("", (cur, next) => cur + "\t" + next.Category)).FirstOrDefault());
                    
                final.ToList().ForEach(Console.WriteLine);
        }
    }
}
